#define BOARD_LENGTH 8
#define SNAKE_MAX_SIZE 32

struct pair {
        int x;
        int y;
};

class Snake {
 private:
        char state;
        int snake_size;
        pair pos[64];
        pair get_next_move(pair p);
        pair fruit_pos;
        bool fruit;

 public:
        Snake() {
                this->state = 'R';
                this->snake_size = 4;
                fruit = false;
                for (int i = 0; i < snake_size ; ++i) {
                        pair p;
                        p.x = 7;
                        p.y = i;
                        this->pos[0] = p;
                }
        }
        void change_state(char state) {
                if (state != 'I') {
                        // if currently moving up or down and state change is
                        // left or right, then update
                        if ((state == 'R' || state == 'L') &&
                            (this->state == 'U' || this->state == 'D')) {
                                this->state = state;
                        }
                        else if ((state == 'U' || state == 'D') &&
                                 (this->state == 'R' || this->state == 'L')) {
                                this->state = state;
                        }
                }
                //else ignore
        }
        void snake_to_matrix(int (*pixels)[BOARD_LENGTH]) {
                for (int i = 0; i < BOARD_LENGTH; ++i) {
                        for (int j = 0; j < BOARD_LENGTH; ++j) {
                                pixels[i][j] = LOW;     // CLEAR ALL PIXELS;
                        }
                }
                for (int i = 0; i < snake_size; ++i) {
                        pair p = pos[i];
                        pixels[p.x][p.y] = HIGH;        // TURN ON PIXELS OF SNAKE
                }
                if (fruit) {
                        pixels[fruit_pos.x][fruit_pos.y] = HIGH;
                }
        }
        pair snake_head() {
                return pos[0];
        }
        void generate_fruit() {
                if (!fruit) {
                        fruit = true;
                        // random inclusive (starts at 0)
                        fruit_pos.x = random(7);
                        fruit_pos.y = random(7);
                }
        }
        void grow();
        void next_move() {
                for (int i = snake_size; i > 0; --i) {
                        pos[i] = pos[i - 1];
                }
                pos[0] = get_next_move(pos[0]);
                if (fruit) {
                        if (pos[0].x == fruit_pos.x && pos[0].y == fruit_pos.y) {
                                grow();
                                fruit = false;
                        }
                }
        }
        bool eats_itself() {
                for (int i = 1; i < snake_size; ++i) {
                        if (pos[0].x == pos[i].x && pos[0].y == pos[i].y) {
                                return true;
                        }
                }
                return false;
        }
};

pair Snake::get_next_move(pair p)
{
        pair r(p);
        switch (state) {
        case 'U':
                r.x = (p.x + BOARD_LENGTH - 1) % BOARD_LENGTH;
                r.y = p.y;
                break;
        case 'D':
                r.x = (p.x + 1) % BOARD_LENGTH;
                r.y = p.y;
                break;
        case 'R':
                r.x = p.x;
                r.y = (p.y + 1) % BOARD_LENGTH;
                break;
        case 'L':
                r.x = p.x;
                r.y = (p.y + BOARD_LENGTH - 1) % BOARD_LENGTH;
                break;
        }
        return r;
}

void Snake::grow()
{
        if (snake_size < SNAKE_MAX_SIZE) {
                pos[snake_size] = pos[snake_size - 1];
                ++snake_size;
        }
}

const int row[8] = { 2, 7, 19, 5, 13, 18, 12, 16 };
const int col[8] = { 6, 11, 10, 3, 17, 4, 8, 9 };

// 2-dimensional array of pixels:
int pixels[8][8];

// cursor position:
int x = 5;
int y = 5;
Snake snake;


// A BIG SAD CROSS
int dead[8][8] = { 
        {HIGH, LOW, LOW, LOW, LOW, LOW, LOW, HIGH},
        {LOW, HIGH, LOW, LOW, LOW, LOW, HIGH, LOW},
        {LOW, LOW, HIGH, LOW, LOW, HIGH, LOW, LOW},
        {LOW, LOW, LOW, HIGH, HIGH, LOW, LOW, LOW},
        {LOW, LOW, LOW, HIGH, HIGH, LOW, LOW, LOW},
        {LOW, LOW, HIGH, LOW, LOW, HIGH, LOW, LOW},
        {LOW, HIGH, LOW, LOW, LOW, LOW, HIGH, LOW},
        {HIGH, LOW, LOW, LOW, LOW, LOW, LOW, HIGH},
};

/**
 * The setup routine runs once when you press reset.
 */
void setup()
{
        Serial.begin(9600); // 9600 bps
        // initialize the I/O pins as outputs
        // iterate over the pins:
        for (int this_pin = 0; this_pin < 8; ++this_pin) {
                // initialize the output pins:
                pinMode(col[this_pin], OUTPUT);
                pinMode(row[this_pin], OUTPUT);
                // take the col pins (i.e. the cathodes) high to ensure that
                // the LEDS are off:
                digitalWrite(col[this_pin], HIGH);
        }

        // initialize the pixel matrix:
        for (int x = 0; x < 8; ++x) {
                for (int y = 0; y < 8; ++y) {
                        pixels[x][y] = HIGH;
                }
        }
        randomSeed(analogRead(0));
}

/**
 * The loop routine runs over and over again forever.
 */
void loop()
{
        snake.generate_fruit();
        snake.next_move();
        snake.snake_to_matrix(pixels);
        for (int i = 0; i < 30; ++i) {
                display_pixels();
                snake.change_state(read_sensors());
        }
        // draw the screen:
        if (snake.eats_itself()) {
                for (int i = 0; i < 3; i++) {
                        for (int j = 0; j < 100; ++j) {
                                display_pixels();
                        }
                        delay(500);
                }
                for (int i = 0; i < 8; ++i) {
                        for (int j = 0; j < 8; ++j) {
                                pixels[i][j] = dead[i][j];
                        }
                }
                while (true) {
                        display_pixels();
                }
        }
}

/**
 * Prints the pixels array in the matrix
 */
void display_pixels()
{
        for (int c = 0; c < 8; ++c) {
                digitalWrite(col[c], LOW);
                for (int r = 0; r < 8; ++r) {
                        digitalWrite(row[r], pixels[r][c]);
                }
                delay(1);
                display_clear();
        }
}

/**
 * Remove empty display light
 * 清空显示
 */
void display_clear()
{
        for (int i = 0; i < 8; ++i) {
                digitalWrite(row[i], LOW);
                digitalWrite(col[i], HIGH);
        }
}

/**
 * Returns the current joystick input with some leeway
 * I means Iddle
 * L means Left
 * R means Right
 * U means Up
 * D means Down
 */
char read_sensors()
{
        //int clicked = digitalRead(z); // if 0, then clicked
        //if(!clicked) return 'C';
        x = map(analogRead(A0), 0, 1023, 0, 10) - 5;
        y = map(analogRead(A1), 0, 1023, 0, 10) - 5;
        // test if joystick is the middle with some leeway
        if (abs(x) <= 3 && abs(y) <= 3) {
                return 'I';
        // test if move in x axis is > than in y axis
        } else if (abs(x) > abs(y)) {
                if (x > 1) {
                        return 'R';
                } else {
                        return 'L';
                }
        } else {
                if (y > 1) {
                        return 'U';
                } else {
                        return 'D';
                }
        }
        return 'I';
}
